﻿namespace Rally_MvvmC.Library
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity;
    using System.Data.Entity.Infrastructure;
    using System.Linq;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http;
    using System.Web.Mvc;
    using Rally_MvvmC.Exceptions;
    using Rally_MvvmC.Exceptions.Server;
    using System.Threading.Tasks;

    public abstract class ApiControllerBase : ApiController
    {
        protected virtual IEnumerable<ValidationResult> ValidateModel(System.Web.Http.ModelBinding.ModelStateDictionary modelState, List<ValidationResult> results)
        {
            if (results == null)
            {
                results = new List<ValidationResult>();
            }

            foreach (var result in results)
            {
                if (result.MemberNames.FirstOrDefault() != null)
                {
                    modelState.AddModelError(result.MemberNames.FirstOrDefault(), result.ErrorMessage);
                }
            }

            return results;
        }


    }
    public abstract class ApiControllerBase<DataStoreType, ViewModelClass, EntityClass> : ApiControllerBase
        where DataStoreType : IDataStore
        where ViewModelClass : ViewModelBase<DataStoreType, EntityClass>
        where EntityClass : class
    {
        private DataStoreType dataStore { get; set; }
        protected ViewModelClass SelectedEntity { get; set; }

        protected virtual Func<DataStoreType> createDataStore
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        protected virtual bool ValidateModel(System.Web.Http.ModelBinding.ModelStateDictionary modelState)
        {
            return this.ValidateModel(modelState, null).Count() == 0 ? true : false;
        }

        public ApiControllerBase()
        {
        }

        protected Rally_MvvmC.Library.DataCollectionBase<DataStoreType, ViewModelClass, EntityClass> DataCollection { get; set; }

        public DataStoreType DataStore 
        {
            get
            {
                if (dataStore == null)
                {
                    dataStore = createDataStore();
                }

                if (dataStore == null)
                {
                    throw new NullReferenceException("dataStore not initialized");
                }
                else
                {
                    return dataStore;
                }
            }
            set
            {
                dataStore = value;
            }
        }

        public virtual IEnumerable<ViewModelClass> GetList()
        {
            return this.DataCollection;
        }
 
        public virtual HttpResponseMessage Delete(int id)
        {
            if (id == 0)
            {
                return Request.CreateResponse(HttpStatusCode.OK);
            }

            var item = this.DataCollection.FirstOrDefault(x => x.Id.Equals(id));
            if (item != null)
            {
                this.DataCollection.GetEntitiesDbContext().Entry(item.Entity).State = System.Data.Entity.EntityState.Deleted;
                try
                {
                    this.DataCollection.GetEntitiesDbContext().SaveChanges();
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    Logger.LogException(ex);
                    return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
                }
                catch (System.Data.Entity.Infrastructure.DbUpdateException ex)
                {
                    Logger.LogException(ex);
                    return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
                }

                return Request.CreateResponse(HttpStatusCode.OK);
            }
            else
            {
                WebApiException ex = new WebApiException("File not found\r\n" + typeof(EntityClass) + "\r\n" + id.ToString());
                Logger.LogException(ex); 
                return Request.CreateErrorResponse(HttpStatusCode.NotFound, ex);
            }
        }

        protected virtual EntityClass LookupById(int id)
        {
            return DataCollection.GetEntity(id);
        }

        public virtual ViewModelClass Get(int id)
        {
            if (id.Equals(0))
            {
                var entityClass = (EntityClass)Activator.CreateInstance(typeof(EntityClass), true);
                var vmc = (ViewModelClass)Activator.CreateInstance(typeof(ViewModelClass), this.DataCollection.GetEntitiesDbContext(), this.DataCollection.GetEntitiesDbSet(), entityClass);
                vmc.DataStore = this.DataStore;
                return vmc;
            }
            else
            {
                var entityClass = this.LookupById(id);
                var vmc = (ViewModelClass)Activator.CreateInstance(typeof(ViewModelClass), this.DataCollection.GetEntitiesDbContext(), this.DataCollection.GetEntitiesDbSet(), entityClass);

                if (entityClass == null)
                {
                    WebApiException ex = new WebApiException(string.Format("Item not found\r\n{0}\r\n{1}", typeof(EntityClass), id.ToString()));
                    Logger.LogException(ex);
                    throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
                }

                return (ViewModelClass)Activator.CreateInstance(typeof(ViewModelClass), this.DataCollection.GetEntitiesDbContext(), DataCollection.GetEntitiesDbSet(), entityClass);
            }
        }

        public virtual HttpResponseMessage Post(int id, ViewModelClass item)
        {
            return this.Put(id, item, 0, false, false);
        }

        public virtual HttpResponseMessage Put(int id, ViewModelClass item)
        {
            return null;
        }

        public virtual HttpResponseMessage Put(int id, ViewModelClass item, int userId, bool updateChildren, bool useTransaction)
        {
            this.SelectedEntity = item as ViewModelClass;
            if (this.SelectedEntity == null)
            {
                var ex = new ArgumentException("item is not valid", "item");
                Logger.LogException(ex);
                if (this.Request == null)
                {
                    return null;
                }
                else
                {
                    return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex);
                }
            }
            else
            {
                this.ValidateModel(this.ModelState);
                if (!ModelState.IsValid)
                {
                    if (this.Request == null)
                    {
                        return null;
                    }
                    else
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
                    }
                }

                if (id != item.Id)
                {
                    if (this.Request == null)
                    {
                        return null;
                    }
                    else
                    {
                        return this.Request.CreateResponse(HttpStatusCode.BadRequest);
                    }
                }

                try
                {
                    DataStore.Put<DataStoreType, ViewModelClass, EntityClass>(this.DataCollection.GetEntitiesDbContext(), item, userId, updateChildren, useTransaction);
                }
                catch (DbUpdateConcurrencyException ex)
                {
                    ModelState.AddModelError("Error", ex);
                    Logger.LogException(ex);
                    if (this.Request == null)
                    {
                        throw ex;
                    }
                    else
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.NotFound, this.ModelState);
                    }
                }
                catch (System.Data.Entity.Infrastructure.DbUpdateException ex)
                {
                    ModelState.AddModelError("Error", ex.InnerException.InnerException);
                    Logger.LogException(ex);
                    if (this.Request == null)
                    {
                        throw ex;
                    }
                    else
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
                    }
                }
                catch (System.Data.Entity.Validation.DbEntityValidationException ex)
                {
                    foreach (var error in ex.EntityValidationErrors)
                    {
                        foreach (var errorMessage in error.ValidationErrors)
                        {
                            ModelState.AddModelError(errorMessage.PropertyName, errorMessage.ErrorMessage);
                        }
                    }

                    Logger.LogException(ex);
                    if (this.Request == null)
                    {
                        throw ex;
                    }
                    else
                    {
                        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, this.ModelState);
                    }
                }

                if (this.Request == null)
                {
                    return null;
                }
                else
                {
                    return this.Request.CreateResponse(HttpStatusCode.OK, item);
                }
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (this.dataStore != null)
            {
                this.dataStore.Dispose();
            }
            base.Dispose(disposing);
        }

        private void SetForSaveChildViewModels(IViewModel item, DbContext dataContext)
        {
            dataContext.Entry(item.GetEntity()).State = System.Data.Entity.EntityState.Modified;
            foreach (var viewModel in item.GetModified())
            {
                this.SetForSaveChildViewModels(viewModel, dataContext);
            }
        }
    }
    
    public class Container<T>
        where T : class
    {
        public T Template { get; set; }

        public IEnumerable<T> Entities { get; set; }
    }
}
